﻿using IdentityServer4.Validation;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Logging;
using Microsoft.IdentityModel.Tokens;
using OA.Infrastructure.Authentication.Abstractions;
using OA.Infrastructure.Authentication.Jwt;
using OA.Infrastructure.Authentication.Redis;
using System.Net;

namespace OA.Infrastructure.Authentication;

public static class ServiceConllectionExtensions
{
    public static IIdentityServerBuilder AddCloudIdentityServer<TValidator>(this IServiceCollection services, IConfiguration configuration)
        where TValidator : class, IResourceOwnerPasswordValidator
    {
        services.AddHttpContextAccessor();
        services.AddOptions<CredentialsOptions>()
            .Configure<IConfiguration>((opts, cfg) =>
            {
                cfg.Bind(CredentialsOptions.Position, opts);
            });

        services.AddOptions<RedisOptions>()
            .Configure<IConfiguration>((opts, cfg) =>
            {
                cfg.Bind(RedisOptions.Position, opts);
            });

        services.AddHttpClient<OAuthEndpointService>().ConfigurePrimaryHttpMessageHandler(provider =>
        {
            return new HttpClientHandler()
            {
                ServerCertificateCustomValidationCallback = delegate { return true; },
                AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
            };
        });

        var builder = services
            .AddIdentityServer()
            .AddClientStore<ClientStore>()
            .AddResourceStore<ResourceStore>();
        //.AddPersistedGrantStore<PersistedGrantStore>();
        builder.AddResourceOwnerValidator<TValidator>();
        builder.AddDeveloperSigningCredential();

        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, opts =>
        {
            opts.BackchannelHttpHandler = new HttpClientHandler()
            {
                ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
            };
            opts.Authority = configuration["IdentityServer:Authority"];
            opts.RequireHttpsMetadata = !string.IsNullOrWhiteSpace(opts.Authority) &&
                opts.Authority.StartsWith("https://", StringComparison.OrdinalIgnoreCase);
            opts.TokenValidationParameters.ValidateAudience = false;
            opts.SaveToken = true;
            opts.IncludeErrorDetails = true;

            opts.Events = new JwtBearerEvents
            {
                OnMessageReceived = ctx =>
                {
                    var accessToken = ctx.Request.Query["access_token"];

                    var path = ctx.HttpContext.Request.Path;
                    if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments(SignalR.SignalRConstants.Path))
                    {
                        ctx.Token = accessToken;
                    }

                    return Task.CompletedTask;
                }
            };

            IdentityModelEventSource.ShowPII = true;
        });

        services.AddNonBreakingSameSiteCookies();

        services.AddSingleton<RedisClient>();

        return builder;
    }

    public static IApplicationBuilder UseCloudIdentityServer(this IApplicationBuilder app)
    {
        app.Use((context, next) =>
        {
            var configuration = context.RequestServices.GetRequiredService<IConfiguration>();

            var forwardOptions = context.RequestServices.GetRequiredService<IOptions<ForwardedHeadersOptions>>();

            if (context.Request.Headers.ContainsKey(forwardOptions.Value.OriginalHostHeaderName))
            {
                var basePath = $"/{configuration["Swagger:SwaggerUrlPrefix"]}";

                context.Request.PathBase = new PathString(basePath);
            }

            return next();
        });

        app.UseIdentityServer();

        return app;
    }

    public static IServiceCollection AddNonBreakingSameSiteCookies(this IServiceCollection services)
    {
        services.Configure<CookiePolicyOptions>(opts =>
        {
            opts.MinimumSameSitePolicy = SameSiteMode.Unspecified;
            opts.Secure = CookieSecurePolicy.Always;
            opts.OnAppendCookie = ctx => CheckSameSite(ctx.Context, ctx.CookieOptions);
            opts.OnDeleteCookie = ctx => CheckSameSite(ctx.Context, ctx.CookieOptions);
        });

        return services;
    }

    private static void CheckSameSite(HttpContext httpContext, CookieOptions options)
    {
        if (options.SameSite == SameSiteMode.None)
        {
            var userAgent = httpContext.Request.Headers["User-Agent"].ToString();

            if (DisallowsSameSiteNone(userAgent))
            {
                options.SameSite = SameSiteMode.Unspecified;
            }
        }
    }

    private static bool DisallowsSameSiteNone(string userAgent)
    {
        return userAgent.Contains("CPU iPhone OS 12") ||
            userAgent.Contains("iPad; CPU OS 12")
|| userAgent.Contains("Safari") &&
            userAgent.Contains("Macintosh; Intel Mac OS X 10_14") &&
            userAgent.Contains("Version/")
|| userAgent.Contains("Chrome/5") || userAgent.Contains("Chrome/6");
    }

    public static IServiceCollection AddJwt(this IServiceCollection services, IConfiguration configuration)
    {
        services.AddHttpContextAccessor();
        services.AddOptions<CredentialsOptions>()
            .Configure<IConfiguration>((opts, cfg) =>
            {
                cfg.Bind(CredentialsOptions.Position, opts);
            });

        services.AddHttpClient<OAuthEndpointService>().ConfigurePrimaryHttpMessageHandler(provider =>
        {
            return new HttpClientHandler()
            {
                ServerCertificateCustomValidationCallback = delegate { return true; },
                AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
            };
        });

        services.AddScoped<UserPoolBase, JwtUserPool>();
        services.AddSingleton<JwtTokenHandler>();

        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, opts =>
            {
                opts.BackchannelHttpHandler = new HttpClientHandler()
                {
                    ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
                };

                var issuer = configuration["Jwt:Issuer"];
                var audience = configuration["Jwt:Audience"];
                var securityKey = configuration["Jwt:SecurityKey"];

                var symmetricKey = new SymmetricSecurityKey(Convert.FromBase64String(securityKey));

                opts.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidIssuer = issuer,
                    ValidAudience = audience,
                    IssuerSigningKey = symmetricKey,

                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidateIssuerSigningKey = true,

                    ValidateLifetime = true,
                    RequireExpirationTime = true,
                    ClockSkew = TimeSpan.FromMinutes(30)
                };

                opts.SaveToken = true;
                opts.IncludeErrorDetails = true;

                opts.Events = new JwtBearerEvents
                {
                    OnMessageReceived = ctx =>
                    {
                        var accessToken = ctx.Request.Query["access_token"];

                        var path = ctx.HttpContext.Request.Path;
                        if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments(SignalR.SignalRConstants.Path))
                        {
                            ctx.Token = accessToken;
                        }

                        return Task.CompletedTask;
                    }
                };

                IdentityModelEventSource.ShowPII = true;
            });

        services.AddTransient<IStartupFilter, JwtAuthenticationMiddleware>();

        services.AddNonBreakingSameSiteCookies();

        return services;
    }
}